package util;

import java.util.ArrayList;
import java.util.Random;

import primatives.PointND;

// generates synthetic data
public class DataGenerator
{
	public static ArrayList<PointND> generateRandom(int w, int h, double elevation)
	{
		ArrayList<PointND> ret = new ArrayList<PointND>();
		Random r = new Random();
		
		for (int x = -w/2; x < w/2; x++)
		{
			for (int y = -h/2; y < h/2; y++)
			{
				double[] values = {x, y, elevation*r.nextDouble()};
				ret.add(new PointND(values));
			}
		}
		
		return ret;
	}
	
	public static ArrayList<PointND> generateSpikes(int w, int h, double elevation, double flat_elevation)
	{
		ArrayList<PointND> ret = new ArrayList<PointND>();
		for (int x = -w/2; x < w/2; x+=2)
		{
			for (int y = -h/2; y < h/2; y+=2)
			{
				double[] values = {x, y, flat_elevation};
				ret.add(new PointND(values));
				
				double[] values2 = {x+1, y, flat_elevation};
				ret.add(new PointND(values2));
				
				double[] values3 = {x, y+1, flat_elevation};
				ret.add(new PointND(values3));
				
				double[] values4 = {x+1, y+1, flat_elevation};
				ret.add(new PointND(values4));
				
				// spike
				double[] spike = {x+0.5, y+0.5, elevation};
				ret.add(new PointND(spike));
			}
		}
		return ret;
	}
	
	public static ArrayList<PointND> generateBedding(int layers, int w, double min, double max, double layer_spacing, double layer_depth_spacing, double surface_max, double surface_min)
	{
		ArrayList<PointND> ret = new ArrayList<PointND>();
		Random r = new Random();
		
		double offset_y = 0.0;
		for (int l = 0; l < layers; l++)
		{
			// create a layer
			int h = (int)(r.nextDouble() * (max - min) + min);
			double depth = r.nextDouble()*layer_depth_spacing;
			for (int x = -w/2; x < w/2; x++)
			{
				for (int y = -h/2; y < h/2; y++)
				{
					double[] values = {x, offset_y+y, r.nextDouble()*(surface_max-surface_min) + surface_min + depth};
					ret.add(new PointND(values));					
				}
			}
			
			offset_y += (h+layer_spacing);
		}
		
		return ret;
	}
	
	// generates a perfectly flat plane
	public static ArrayList<PointND> generatePerfectFlat(int w, int h, double elevation)
	{
		ArrayList<PointND> ret = new ArrayList<PointND>();
		for (int x = -w/2; x < w/2; x++)
		{
			for (int y = -h/2; y < h/2; y++)
			{
				double[] values = {x, y, elevation};
				ret.add(new PointND(values));

			}
		}
		return ret;
	}
	
	// generates a plane in which all elevations values are within plus/minus tolerance
	public static ArrayList<PointND> generateSemiFlat(int w, int h, double elevation, double tolerance)
	{
		ArrayList<PointND> ret = new ArrayList<PointND>();
		Random r = new Random();
		
		for (int x = -w/2; x < w/2; x++)
		{
			for (int y = -h/2; y < h/2; y++)
			{
				double[] values = {x, y, elevation + r.nextDouble()*tolerance};
				ret.add(new PointND(values));
			}
		}
		return ret;
	}
	
	// TODO: finish half sphere method
	public static ArrayList<PointND> generateHalfSphere(double max_radius, int slices)
	{
		double start_height = -500.0;
		ArrayList<PointND> ret = new ArrayList<PointND>();
		PointND top = new PointND(3);
		top.set_coord(0, 0.0);
		top.set_coord(1, 0.0);
		top.set_coord(2, start_height);
		ret.add(top);
		
		
		
		return ret;
	}
	
	public static ArrayList<PointND> generateHalfCylinder(int w, int h, double scale_factor)
	{
		ArrayList<PointND> ret = new ArrayList<PointND>();
		
		double z = 0.0;
		for (int x = -w/2; x < w/2; x++)
		{
			for (int y = -h/2; y < h/2; y++)
			{
				double[] values = {x, y, -scale_factor*y*y};
				ret.add(new PointND(values));
			}
		}
		
		return ret;
	}
	
	public static ArrayList<PointND> generateCube(int sidelength)
	{
		ArrayList<PointND> ret = new ArrayList<PointND>();
		
		// left side face		
		PointND v1 = new PointND(3);
		v1.set_coord(0, 0);
		v1.set_coord(1, sidelength);
		v1.set_coord(2, 0);
		ret.add(v1);
		
		PointND v2 = new PointND(3);
		v2.set_coord(0, 0);
		v2.set_coord(1, sidelength);
		v2.set_coord(2, sidelength);
		ret.add(v2);
		
		PointND v3 = new PointND(3);
		v3.set_coord(0, 0);
		v3.set_coord(1, 0);
		v3.set_coord(2, sidelength);
		ret.add(v3);
		
		PointND v4 = new PointND(3);
		v4.set_coord(0, 0);
		v4.set_coord(1, 0);
		v4.set_coord(2, 0);
		ret.add(v4);
		
		// right side face
		PointND v5 = new PointND(3);
		v5.set_coord(0, sidelength);
		v5.set_coord(1, 0);
		v5.set_coord(2, 0);
		ret.add(v5);
		
		PointND v6 = new PointND(3);
		v6.set_coord(0, sidelength);
		v6.set_coord(1, sidelength);
		v6.set_coord(2, 0);
		ret.add(v6);
		
		PointND v7 = new PointND(3);
		v7.set_coord(0, sidelength);
		v7.set_coord(1, sidelength);
		v7.set_coord(2, sidelength);
		ret.add(v7);
		
		PointND v8 = new PointND(3);
		v8.set_coord(0, sidelength);
		v8.set_coord(1, 0);
		v8.set_coord(2, sidelength);
		ret.add(v8);
		
		return ret;
	}
	
	public static ArrayList<PointND> generatePyramid(double center_height, double offset)
	{
		ArrayList<PointND> ret = new ArrayList<PointND>();

		double x = 50;
		double y = 50;
		double flat_elevation = center_height-offset;
		
		double[] values = {x, y, flat_elevation};
		ret.add(new PointND(values));
		
		double[] values2 = {x+x, y, flat_elevation};
		ret.add(new PointND(values2));
		
		double[] values3 = {x, y+y, flat_elevation};
		ret.add(new PointND(values3));
		
		double[] values4 = {x+x, y+y, flat_elevation};
		ret.add(new PointND(values4));
		
		// spike
		double[] spike = {x+x/2.0, y+y/2.0, center_height};
		ret.add(new PointND(spike));
		
		return ret;
	}
	
	public static ArrayList<PointND> generateCone(double base_radius, double height, double degree_increments)
	{
		ArrayList<PointND> ret = new ArrayList<PointND>();

		double base = -height;
		
		// base circle
		for (int i = 0; i < 360; i+=(int)degree_increments)
		{
			double x = base_radius * Math.cos(Math.toRadians(i));
			double y = base_radius * Math.sin(Math.toRadians(i));
			double[] v = {x, y, base};
			ret.add(new PointND(v));
		}
		
		
		// spike
		double[] spike = {0, 0, 0};
		ret.add(new PointND(spike));
		
		return ret; 
	}
	
	
	// take two point sets and stich them together with p2 to the left of p1
 	public static ArrayList<PointND> sticheLeft(ArrayList<PointND> p1, ArrayList<PointND> p2)
	{
		ArrayList<PointND> ret = new ArrayList<PointND>(p1);
		double min_x = Double.MAX_VALUE;
		double min_y = Double.MAX_VALUE;
		double max_x = Double.MIN_VALUE;
		double max_y = Double.MIN_VALUE;
		for (PointND p: p1)
		{
			double x = p.get_coord(0);
			double y = p.get_coord(1);
			if (x < min_x) min_x = x;
			if (y < min_y) min_y = y;
			if (x > max_x) max_x = x;
			if (y > max_y) max_y = y;
		}
		
		// shift all the x coord values
		for (PointND q: p2)
		{
			double x = q.get_coord(0);
			q.set_coord(0, x+max_x*2);
		}
		ret.addAll(p2);
		
		return ret;
	}
}
